﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Configuration;
using System.IO;
using System.Net;
using System.Xml;
using System.Reflection;
using Org.BouncyCastle.Bcpg.OpenPgp;
using Org.BouncyCastle.Bcpg;
using Org.BouncyCastle.Security;
using System.Linq;

namespace Vision.Core.ActivityComponents.Encryption.PGP
{
    /// <summary>
    /// 
    /// </summary>
    public class PGPWrapper
    {        
        /// <summary>
        /// 
        /// </summary>
        /// <param name="inputStream"></param>
        /// <param name="keyIn"></param>
        public static void VerifyFile(Stream inputStream, Stream keyIn)
        {
            inputStream = PgpUtilities.GetDecoderStream(inputStream);

            PgpObjectFactory pgpFact = new PgpObjectFactory(inputStream);
            PgpCompressedData c1 = (PgpCompressedData)pgpFact.NextPgpObject();
            pgpFact = new PgpObjectFactory(c1.GetDataStream());

            PgpOnePassSignatureList p1 = (PgpOnePassSignatureList)pgpFact.NextPgpObject();

            PgpOnePassSignature ops = p1[0];

            PgpLiteralData p2 = (PgpLiteralData)pgpFact.NextPgpObject();
            Stream dIn = p2.GetInputStream();
            PgpPublicKeyRingBundle pgpRing = new PgpPublicKeyRingBundle(PgpUtilities.GetDecoderStream(keyIn));
            PgpPublicKey key = pgpRing.GetPublicKey(ops.KeyId);
            FileStream fos = File.Create(p2.FileName);

            ops.InitVerify(key);

            int ch;
            while ((ch = dIn.ReadByte()) >= 0)
            {
                ops.Update((byte)ch);
                fos.WriteByte((byte)ch);
            }
            fos.Close();

            PgpSignatureList p3 = (PgpSignatureList)pgpFact.NextPgpObject();
            PgpSignature firstSig = p3[0];
            if (ops.Verify(firstSig))
            {
                Console.Out.WriteLine("signature verified.");
            }
            else
            {
                Console.Out.WriteLine("signature verification failed.");
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="inputFileName"></param>
        /// <param name="inputFileStream"></param>
        /// <param name="publicKeyStream"></param>
        /// <returns></returns>
		public static byte[] EncryptStream(string inputFileName, bool withIntegrityPacket, Stream inputFileStream, Stream publicKeyStream)
        {
            byte[] returnArray;
            PgpPublicKey publicKey = ReadPublicKey(publicKeyStream);
            using (MemoryStream encryptedStream = new MemoryStream()){                
                PgpCompressedDataGenerator comData = new PgpCompressedDataGenerator(CompressionAlgorithmTag.Zip);
                PgpUtilities.WriteFileToLiteralData(comData.Open(encryptedStream), PgpLiteralData.Binary, inputFileStream, inputFileName);                                                
                comData.Close();

				PgpEncryptedDataGenerator cPk = new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Cast5, withIntegrityPacket, new SecureRandom());
                cPk.AddMethod(publicKey);

                byte[] bytes = encryptedStream.ToArray();

                using (MemoryStream mStream = new MemoryStream()){
                    Stream cOut = cPk.Open(mStream, bytes.Length);
                    cOut.Write(bytes, 0, bytes.Length);
                    cOut.Close();

                    mStream.Seek(0, SeekOrigin.Begin);
                    returnArray = mStream.ToArray();                                
                }                
            }
            return returnArray;
        }

		/// <summary>
		/// 
		/// </summary>
		/// <param name="inputFileName"></param>
		/// <param name="inputFileStream"></param>
		/// <param name="publicKeyStream"></param>
		/// <param name="publicKeyStream"></param>
		/// <returns></returns>
		public static byte[] EncryptStream(string inputFileName, bool withIntegrityPacket, Stream inputFileStream, Stream publicKeyStream, string user)
		{
			byte[] returnArray;
			PgpPublicKey publicKey = ReadPublicKey(publicKeyStream, user);
			using (MemoryStream encryptedStream = new MemoryStream())
			{
				PgpCompressedDataGenerator comData = new PgpCompressedDataGenerator(CompressionAlgorithmTag.Zip);
				PgpUtilities.WriteFileToLiteralData(comData.Open(encryptedStream), PgpLiteralData.Binary, inputFileStream, inputFileName);
				comData.Close();

				PgpEncryptedDataGenerator cPk = new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Cast5, withIntegrityPacket, new SecureRandom());
				cPk.AddMethod(publicKey);

				byte[] bytes = encryptedStream.ToArray();

				using (MemoryStream mStream = new MemoryStream())
				{
					Stream cOut = cPk.Open(mStream, bytes.Length);
					cOut.Write(bytes, 0, bytes.Length);
					cOut.Close();

					mStream.Seek(0, SeekOrigin.Begin);
					returnArray = mStream.ToArray();
				}
			}
			return returnArray;
		}

        /// <summary>
        /// 
        /// </summary>
        /// <param name="inputFileStream"></param>
        /// <param name="privateKeyStream"></param>
        /// <param name="passPhrase"></param>
        /// <param name="outputFileStream"></param>
        /// <param name="outputFileName"></param>
        public static void DecryptStream(Stream inputFileStream, Stream privateKeyStream, string passPhrase, out byte[] outputFileData, out string outputFileName)
        {
            byte[] fileData = new byte[0];
            string fileName = string.Empty;
            
            Stream outputFileStream = null;
            DecryptFileAsStream(inputFileStream, privateKeyStream, passPhrase.ToCharArray(),out outputFileStream, out fileName);
            if(outputFileStream != null){
                fileData = new byte[outputFileStream.Length];
                outputFileStream.Read(fileData, 0, fileData.Length);
            }
            outputFileStream.Dispose();
            outputFileStream = null;

            outputFileData = fileData;
            outputFileName = fileName;
        }
                
        /// <summary>
        /// 
        /// </summary>
        /// <param name="outputStream"></param>
        /// <param name="fileName"></param>
        /// <param name="encKey"></param>
        /// <param name="armor"></param>
        /// <param name="withIntegrityCheck"></param>
        private static void EncryptFile(Stream outputStream, string fileName, PgpPublicKey encKey, bool armor, bool withIntegrityCheck)
        {
            if (armor){
                outputStream = new ArmoredOutputStream(outputStream);
            }

            try{
                MemoryStream bOut = new MemoryStream();
                PgpCompressedDataGenerator comData = new PgpCompressedDataGenerator(CompressionAlgorithmTag.Zip);
                PgpUtilities.WriteFileToLiteralData(comData.Open(bOut), PgpLiteralData.Binary, new FileInfo(fileName));

                comData.Close();

                PgpEncryptedDataGenerator cPk = new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Cast5, withIntegrityCheck, new SecureRandom());

                cPk.AddMethod(encKey);

                byte[] bytes = bOut.ToArray();

                Stream cOut = cPk.Open(outputStream, bytes.Length);

                cOut.Write(bytes, 0, bytes.Length);

                cOut.Close();

                if (armor){
                    outputStream.Close();
                }
            }
            catch (PgpException e){
                Console.Error.WriteLine(e);

                Exception underlyingException = e.InnerException;
                if (underlyingException != null){
                    Console.Error.WriteLine(underlyingException.Message);
                    Console.Error.WriteLine(underlyingException.StackTrace);
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="inputStream"></param>
        /// <param name="keyIn"></param>
        /// <param name="passwd"></param>
        /// <param name="outputStream"></param>
        /// <param name="fileName"></param>
        private static void DecryptFileAsStream(Stream inputStream, Stream keyIn, char[] passwd, out Stream outputFileStream, out string outputFileName)
        {
            inputStream = PgpUtilities.GetDecoderStream(inputStream);
            outputFileStream = new MemoryStream();
            outputFileName = string.Empty;

            try
            {
                PgpObjectFactory pgpF = new PgpObjectFactory(inputStream);
                PgpEncryptedDataList enc;

                PgpObject o = pgpF.NextPgpObject();
                //
                // the first object might be a PGP marker packet.
                //
                if (o is PgpEncryptedDataList){
                    enc = (PgpEncryptedDataList)o;
                }
                else{
                    enc = (PgpEncryptedDataList)pgpF.NextPgpObject();
                }

                //
                // find the secret key
                //
                PgpPrivateKey sKey = null;
                PgpPublicKeyEncryptedData pbe = null;

                foreach (PgpPublicKeyEncryptedData pked in enc.GetEncryptedDataObjects()){
                    sKey = FindSecretKey(keyIn, pked.KeyId, passwd);
                    if (sKey != null){
                        pbe = pked;
                        break;
                    }
                }
                
                if (sKey == null){
                    throw new ArgumentException("secret key for message not found.");
                }

                Stream clear = pbe.GetDataStream(sKey);
                PgpObjectFactory plainFact = new PgpObjectFactory(clear);
                PgpObject message = plainFact.NextPgpObject();

                if (message is PgpCompressedData){
                    PgpCompressedData cData = (PgpCompressedData)message;
                    PgpObjectFactory pgpFact = new PgpObjectFactory(cData.GetDataStream());

                    message = pgpFact.NextPgpObject();

                    if (message is PgpOnePassSignatureList){
                        PgpOnePassSignatureList p1 = (PgpOnePassSignatureList)message;
                        PgpOnePassSignature ops = p1[0];
                        message = pgpFact.NextPgpObject();
                    }
                }
                if (message is PgpLiteralData){
                    PgpLiteralData ld = (PgpLiteralData)message;
                    Stream unc = ld.GetInputStream();

                    int ch;
                    while ((ch = unc.ReadByte()) >= 0){
                        outputFileStream.WriteByte((byte)ch);
                    }
                    outputFileStream.Seek(0, SeekOrigin.Begin);
                    outputFileName = ld.FileName;
                }
                else{
                    throw new PgpException("message is not a simple encrypted file - type unknown.");
                }

                if (pbe.IsIntegrityProtected()){
                    if (!pbe.Verify()){
                        Console.Error.WriteLine("message failed integrity check");
                    }
                    else{
                        Console.Error.WriteLine("message integrity check passed");
                    }
                }
                else{
                    Console.Error.WriteLine("no message integrity check");
                }                
            }
            catch (PgpException e){
                Console.Error.WriteLine(e);
                Exception underlyingException = e.InnerException;
                if (underlyingException != null){
                    Console.Error.WriteLine(underlyingException.Message);
                    Console.Error.WriteLine(underlyingException.StackTrace);
                }
                throw e;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="inputStream"></param>
        /// <param name="keyIn"></param>
        /// <param name="passwd"></param>
        /// <param name="tempDir"></param>
        private static void DecryptFile(Stream inputStream, Stream keyIn, char[] passwd, string tempDir)
        {
            inputStream = PgpUtilities.GetDecoderStream(inputStream);

            try
            {
                PgpObjectFactory pgpF = new PgpObjectFactory(inputStream);
                PgpEncryptedDataList enc;

                PgpObject o = pgpF.NextPgpObject();
                //
                // the first object might be a PGP marker packet.
                //
                
                if (o is PgpEncryptedDataList){
                    enc = (PgpEncryptedDataList)o;
                }
                else{
                    enc = (PgpEncryptedDataList)pgpF.NextPgpObject();
                }

                //
                // find the secret key
                //
                PgpPrivateKey sKey = null;
                PgpPublicKeyEncryptedData pbe = null;

                foreach (PgpPublicKeyEncryptedData pked in enc.GetEncryptedDataObjects()){
                    sKey = FindSecretKey(keyIn, pked.KeyId, passwd);
                    if (sKey != null){
                        pbe = pked;
                        break;
                    }
                }
                                
                if (sKey == null){
                    throw new ArgumentException("secret key for message not found.");
                }

                Stream clear = pbe.GetDataStream(sKey);

                PgpObjectFactory plainFact = new PgpObjectFactory(clear);

                PgpObject message = plainFact.NextPgpObject();

                if (message is PgpCompressedData){
                    PgpCompressedData cData = (PgpCompressedData)message;
                    PgpObjectFactory pgpFact = new PgpObjectFactory(cData.GetDataStream());
                    message = pgpFact.NextPgpObject();
                }

                if (message is PgpOnePassSignatureList){                    
                    message = plainFact.NextPgpObject();
                }

                if (message is PgpLiteralData){
                    PgpLiteralData ld = (PgpLiteralData)message;
                    //System.Diagnostics.EventLog.WriteEntry("PGPWrapper[Decrypt] DEBUG", "Decrypting to: " + ld.FileName);
                    using (FileStream fOut = File.Create(Path.Combine(tempDir, ld.FileName))){
                        //System.Diagnostics.EventLog.WriteEntry("PGPWrapper[Decrypt] DEBUG", "Decrypted to: " + fOut.Name);
                        Stream unc = ld.GetInputStream();
                        int ch;
                        while ((ch = unc.ReadByte()) >= 0){
                            fOut.WriteByte((byte)ch);
                        }
                        fOut.Close();
                    }
                }
                else{
                    throw new PgpException("message is not a simple encrypted file - type unknown.");
                }

                if (pbe.IsIntegrityProtected()){
                    if (!pbe.Verify()){
                        Console.Error.WriteLine("message failed integrity check");
                    }
                    else{
                        Console.Error.WriteLine("message integrity check passed");
                    }
                }
                else{
                    Console.Error.WriteLine("no message integrity check");
                }
            }
            catch (PgpException e){
                Console.Error.WriteLine(e);
                Exception underlyingException = e.InnerException;
                if (underlyingException != null){
                    Console.Error.WriteLine(underlyingException.Message);
                    Console.Error.WriteLine(underlyingException.StackTrace);
                }
            }
        }
        
        /// <summary>
        /// 
        /// </summary>
        /// <param name="inputStream"></param>
        /// <returns></returns>
        private static PgpPublicKey ReadPublicKey(Stream inputStream)
        {
            inputStream = PgpUtilities.GetDecoderStream(inputStream);
            PgpPublicKeyRingBundle pgpPub = new PgpPublicKeyRingBundle(inputStream);

            foreach (PgpPublicKeyRing kRing in pgpPub.GetKeyRings()){
                foreach (PgpPublicKey k in kRing.GetPublicKeys()){
                    if (k.IsEncryptionKey){
                        return k;
                    }
                }
            }

            throw new ArgumentException("Can't find encryption key in key ring.");
        }

		/// <summary>
		/// 
		/// </summary>
		/// <param name="inputStream"></param>
		/// <returns></returns>
		private static PgpPublicKey ReadPublicKey(Stream inputStream, string user)
		{
			inputStream = PgpUtilities.GetDecoderStream(inputStream);
			PgpPublicKeyRingBundle pgpPub = new PgpPublicKeyRingBundle(inputStream);

			foreach (PgpPublicKeyRing kRing in pgpPub.GetKeyRings(user, true, true))
			{
				foreach (PgpPublicKey k in kRing.GetPublicKeys())
				{
					if (k.IsEncryptionKey)
					{
						return k;
					}
				}
			}

			throw new ArgumentException("Can't find encryption key in key ring.");
		}

        /// <summary>
        /// 
        /// </summary>
        /// <param name="keyIn"></param>
        /// <param name="keyId"></param>
        /// <param name="pass"></param>
        /// <returns></returns>
        private static PgpPrivateKey FindSecretKey(Stream keyIn, long keyId, char[] pass)
        {
            PgpSecretKeyRingBundle pgpSec = new PgpSecretKeyRingBundle(PgpUtilities.GetDecoderStream(keyIn));
            PgpSecretKey pgpSecKey = pgpSec.GetSecretKey(keyId);
            if (pgpSecKey == null){
                return null;
            }

            return pgpSecKey.ExtractPrivateKey(pass);
        }
    }    
}
